package my.db;

import my.mvc.RequestContext;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Created by hackway on 2016/5/16.
 */
public class DBManager {

    private final static Logger logger = LoggerFactory.getLogger(DBManager.class);
    private final static ThreadLocal<Connection> conns = new ThreadLocal<>();
    private static DataSource dataSource;
    private static boolean show_sql = false;
    private static Properties props = new Properties();
    final static ConcurrentHashMap<Long, ConnectionContext> conn_context;

    public static class ConnectionContext {
        public Exception exception;
        public Map<String, String[]> params;
        public String thread;
        public String uri;
        public String ip;
        public ConnectionContext(Exception exception, String ip, String uri, Map<String, String[]> params) {
            this.exception = exception;
            this.ip = ip;
            this.params = params;
            this.uri = uri;
            this.thread = Thread.currentThread().getName();
        }

        public Exception getException() {
            return exception;
        }

        public Map<String, String[]> getParams() {
            return params;
        }

        public String getThread() {
            return thread;
        }

        public String getUri() {
            return uri;
        }

        public String getIp() {
            return ip;
        }
    }

    public static class PoolStatus {
        public int total = -1;  //总连接数
        public int busy = -1;  //活动连接
        public int idle = -1;  //空闲连接
        public Map<Long, ConnectionContext> conns;

        public int getTotal() {
            return total;
        }

        public int getBusy() {
            return busy;
        }

        public int getIdle() {
            return idle;
        }

        public Map<Long, ConnectionContext> getConns() {
            return conns;
        }
    }

    static {
        conn_context = new ConcurrentHashMap<Long, ConnectionContext>();
        initDataSource(null); //初始化数据库连接池
    }

    private final static void initDataSource(Properties dbProperties) {
        try {
            if (dbProperties == null) {
                dbProperties = new Properties();
                dbProperties.load(DBManager.class.getResourceAsStream("bonecp.properties"));
            }
            for (Object key : dbProperties.keySet()) {
                String skey = (String) key;
                if (skey.startsWith("jdbc.")) {
                    String name = skey.substring(5);
                    props.put(name, dbProperties.getProperty(skey));
                    if ("show_sql".equalsIgnoreCase(name)) {
                        show_sql = "true".equalsIgnoreCase(dbProperties.getProperty(skey));
                    }
                }
            }
            dataSource = (DataSource) Class.forName(props.getProperty("datasource")).newInstance();
            if (dataSource.getClass().getName().indexOf("c3p0") > 0) {
                //Disable JMX in C3P0
                System.setProperty("com.mchange.v2.c3p0.management.ManagementCoordinator",
                        "com.mchange.v2.c3p0.management.NullManagementCoordinator");
            }
            logger.info("Using DataSource : "+dataSource.getClass().getName());
            BeanUtils.populate(dataSource, props);
            Connection conn = getConnection();
            DatabaseMetaData mdm = conn.getMetaData();
            logger.info("Connected to "+mdm.getDatabaseProductName()+" "+mdm.getDatabaseProductVersion());
            closeConnection();
        } catch (IOException | InstantiationException | InvocationTargetException | SQLException
                | IllegalAccessException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    //获取链接池状态
    public static PoolStatus getPoolStatus() {
        PoolStatus pool = new PoolStatus();
        try {
            if (dataSource.getClass().getName().contains("c3p0")) {
                pool.total = (Integer) PropertyUtils.getProperty(dataSource, "numConnectionsDefaultUser");
                pool.busy = (Integer) PropertyUtils.getProperty(dataSource, "numBusyConnectionsDefaultUser");
                pool.idle = (Integer) PropertyUtils.getProperty(dataSource, "numIdleConnectionsDefaultUser");
            } else if(dataSource.getClass().getName().contains("bonecp")) {
                pool.total = (Integer)PropertyUtils.getProperty(dataSource, "maxConnectionsPerPartition") * (Integer)PropertyUtils.getProperty(dataSource, "partitionCount");
                //pool.busy = (Integer)PropertyUtils.getProperty(dataSource, "numBusyConnectionsDefaultUser");
                //pool.idle = (Integer)PropertyUtils.getProperty(dataSource, "numIdleConnectionsDefaultUser");
            } else if(dataSource.getClass().getName().contains("druid")) {
                pool.total = (Integer)PropertyUtils.getProperty(dataSource, "maxActive");
                pool.busy = (Integer)PropertyUtils.getProperty(dataSource, "activeCount");
                pool.idle = (Integer)PropertyUtils.getProperty(dataSource, "poolingCount");
            }
            pool.conns = conn_context;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pool;
    }


    public final static void closeDataSource() {
        try {
            dataSource.getClass().getMethod("close").invoke(dataSource);
        } catch (NoSuchMethodException e) {

        } catch (Exception e) {
            logger.error("Unabled to destroy DataSource!!! ", e);
        }
    }

    //从连接池中获取连接
    public final static Connection getConnection() throws SQLException {
        Connection conn = conns.get();
        if (conn == null || conn.isClosed()) {
            conn = _getConnection();
            if (conn == null)
                throw new SQLException("Unabled to get connection.");
            conns.set(conn);
            RequestContext ctx = RequestContext.get();
            conn_context.put(Thread.currentThread().getId(), new ConnectionContext(new Exception(),
                    (ctx!=null)?ctx.ip():null,
                    (ctx!=null)?ctx.uri():null,
                    (ctx!=null)?ctx.request().getParameterMap():null));
        }
        return (show_sql && !Proxy.isProxyClass(conn.getClass()))?new _debugConnection(conn).getConnection():conn;
    }
    private static Connection _getConnection() {
        try {
            return dataSource.getConnection();
        } catch (Exception e) {
            logger.error("Unable to get connection from datasource", e);
            return null;
        }
    }

    //关闭连接
    public final static void closeConnection() {
        Connection conn = conns.get();
        try {
            if (conn != null && !conn.isClosed()) {
                conn.setAutoCommit(true);
                conn.close();
                conn_context.remove(Thread.currentThread().getId());
            }
        } catch (SQLException e) {
            logger.error("Unabled to close connection!!! ", e);
        }
        conns.set(null);
    }

    //debug环境
    private final static class _debugConnection implements InvocationHandler {
        private final static Logger log = LoggerFactory.getLogger(_debugConnection.class);
        private Connection conn = null;
        public _debugConnection(Connection conn) {
            this.conn = conn;
        }
        public Connection getConnection() {
            return (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            try {
                String m = method.getName();
                if("prepareStatement".equals(method) || "createStatement".equals(method))
                    logger.info("[SQL] >>> " + args[0]);
                return method.invoke(conn, args);
            } catch (InvocationTargetException e) {
                throw e.getTargetException();
            }
        }
    }
}
